/* * Created on Dec 5, 2012 * Created by Paul Gardner * * Copyright 2012 Vuze, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License only. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */ package com.aelitis.azureus.core.pairing.impl; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.math.BigInteger; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URL; import java.security.AlgorithmParameters; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.bouncycastle.crypto.agreement.srp.SRP6Server; import org.bouncycastle.crypto.agreement.srp.SRP6VerifierGenerator; import org.bouncycastle.crypto.digests.SHA256Digest; import org.gudy.azureus2.core3.internat.MessageText; import org.gudy.azureus2.core3.util.Base32; import org.gudy.azureus2.core3.util.ByteFormatter; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.FileUtil; import org.gudy.azureus2.core3.util.IndentWriter; import org.gudy.azureus2.core3.util.RandomUtils; import org.gudy.azureus2.core3.util.SimpleTimer; import org.gudy.azureus2.core3.util.SystemTime; import org.gudy.azureus2.core3.util.TimerEvent; import org.gudy.azureus2.core3.util.TimerEventPerformer; import org.gudy.azureus2.core3.util.UrlUtils; import org.gudy.azureus2.plugins.PluginInterface; import org.gudy.azureus2.plugins.tracker.web.TrackerWebPageRequest; import org.gudy.azureus2.plugins.tracker.web.TrackerWebPageResponse; import org.json.simple.JSONObject; import com.aelitis.azureus.core.AzureusCore; import com.aelitis.azureus.core.dht.DHT; import com.aelitis.azureus.core.dht.nat.DHTNATPuncher; import com.aelitis.azureus.core.dht.nat.DHTNATPuncherListener; import com.aelitis.azureus.core.dht.transport.DHTTransportContact; import com.aelitis.azureus.core.nat.NATTraversalHandler; import com.aelitis.azureus.core.nat.NATTraverser; import com.aelitis.azureus.core.pairing.PairedServiceRequestHandler; import com.aelitis.azureus.core.pairing.impl.PairingManagerImpl.PairedServiceImpl; import com.aelitis.azureus.core.security.CryptoManager; import com.aelitis.azureus.core.security.CryptoManagerFactory; import com.aelitis.azureus.plugins.dht.DHTPlugin; import com.aelitis.azureus.util.JSONUtils; public class PairingManagerTunnelHandler { private static final String DEFAULT_IDENTITY = "vuze"; private BigInteger N_3072; private BigInteger G_3072; private byte[] SRP_SALT; private BigInteger SRP_VERIFIER; private PairingManagerImpl manager; private AzureusCore core; private boolean started = false; private boolean active = false; private List<DHTNATPuncher> nat_punchers_ipv4 = new ArrayList<DHTNATPuncher>(); private List<DHTNATPuncher> nat_punchers_ipv6 = new ArrayList<DHTNATPuncher>(); private int last_punchers_registered = 0; private TimerEvent update_event; private Map<String,Object[]> local_server_map = new LinkedHashMap<String,Object[]>( 10, 0.75f, true ) { protected boolean removeEldestEntry( Map.Entry<String,Object[]> eldest) { return size() > 10; } }; private long last_server_create_time; private long last_server_agree_time; private int total_servers; private long last_local_server_create_time; private long last_local_server_agree_time; private int total_local_servers; private static final int MAX_TUNNELS = 10; private Map<String,PairManagerTunnel> tunnels = new HashMap<String,PairManagerTunnel>(); private String init_fail; protected PairingManagerTunnelHandler( PairingManagerImpl _manager, AzureusCore _core ) { manager = _manager; core = _core; CryptoManager.SRPParameters params = CryptoManagerFactory.getSingleton().getSRPParameters(); if ( params != null ){ SRP_SALT = params.getSalt(); SRP_VERIFIER = params.getVerifier(); } } public void setSRPPassword( char[] password ) { if ( password == null || password.length == 0 ){ SRP_SALT = null; SRP_VERIFIER = null; CryptoManagerFactory.getSingleton().setSRPParameters( null, null ); }else{ start(); try{ byte[] I = DEFAULT_IDENTITY.getBytes( "UTF-8" ); byte[] P = new String(password).getBytes( "UTF-8" ); byte[] salt = new byte[16]; RandomUtils.nextSecureBytes( salt ); SRP6VerifierGenerator gen = new SRP6VerifierGenerator(); gen.init( N_3072, G_3072, new SHA256Digest()); BigInteger verifier = gen.generateVerifier( salt, I, P ); CryptoManagerFactory.getSingleton().setSRPParameters( salt, verifier ); SRP_SALT = salt; SRP_VERIFIER = verifier; }catch( Throwable e ){ Debug.out( e ); } } updateActive(); } private void start() { synchronized( this ){ if ( started ){ return; } started = true; } N_3072 = fromHex( "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08" + "8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B" + "302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9" + "A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6" + "49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8" + "FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C" + "180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718" + "3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D" + "04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D" + "B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226" + "1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" + "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC" + "E0FD108E 4B82D120 A93AD2CA FFFFFFFF FFFFFFFF" ); G_3072 = BigInteger.valueOf(5); try{ PluginInterface dht_pi = core.getPluginManager().getPluginInterfaceByClass( DHTPlugin.class ); if ( dht_pi == null ){ throw( new Exception( "DHT Plugin not found" )); } DHTPlugin dht_plugin = (DHTPlugin)dht_pi.getPlugin(); if ( !dht_plugin.isEnabled()){ throw( new Exception( "DHT Plugin is disabled" )); } DHT[] dhts = dht_plugin.getDHTs(); List<DHTNATPuncher> punchers = new ArrayList<DHTNATPuncher>(); for ( DHT dht: dhts ){ int net = dht.getTransport().getNetwork(); if ( net == DHT.NW_MAIN ){ DHTNATPuncher primary_puncher = dht.getNATPuncher(); if ( primary_puncher != null ){ punchers.add( primary_puncher ); nat_punchers_ipv4.add( primary_puncher ); for ( int i=1;i<=2; i++ ){ DHTNATPuncher puncher = primary_puncher.getSecondaryPuncher(); punchers.add( puncher ); nat_punchers_ipv4.add( puncher ); } } }else if ( net == DHT.NW_MAIN_V6 ){ /* * no point in this atm as we don't support v6 tunnels DHTNATPuncher puncher = dht.getNATPuncher(); if ( puncher != null ){ punchers.add( puncher ); nat_punchers_ipv6.add( puncher ); puncher = puncher.getSecondaryPuncher(); punchers.add( puncher ); nat_punchers_ipv6.add( puncher ); } */ } } if ( punchers.size() == 0 ){ throw( new Exception( "No suitable DHT instances available" )); } for ( DHTNATPuncher p: punchers ){ p.forceActive( true ); p.addListener( new DHTNATPuncherListener() { public void rendezvousChanged( DHTTransportContact rendezvous ) { System.out.println( "active: " + rendezvous.getString()); synchronized( PairingManagerTunnelHandler.this ){ if ( update_event == null ){ update_event = SimpleTimer.addEvent( "PMT:defer", SystemTime.getOffsetTime( 15*1000 ), new TimerEventPerformer() { public void perform( TimerEvent event) { synchronized( PairingManagerTunnelHandler.this ){ update_event = null; } System.out.println( " updating" ); manager.updateNeeded(); }; }); } } } }); } core.getNATTraverser().registerHandler( new NATTraversalHandler() { private Map<Long,Object[]> server_map = new LinkedHashMap<Long,Object[]>( 10, 0.75f, true ) { protected boolean removeEldestEntry( Map.Entry<Long,Object[]> eldest) { return size() > 10; } }; public int getType() { return( NATTraverser.TRAVERSE_REASON_PAIR_TUNNEL ); } public String getName() { return( "Pairing Tunnel" ); } public Map process( InetSocketAddress originator, Map data ) { if ( SRP_VERIFIER == null || !active ){ return( null ); } boolean good_request = false; try{ Map result = new HashMap(); Long session = (Long)data.get( "sid" ); if ( session == null ){ return( null ); } InetAddress tunnel_originator; try{ tunnel_originator = InetAddress.getByAddress( (byte[])data.get( "origin")); }catch( Throwable e ){ Debug.out( "originator decode failed: " + data ); return( null ); } System.out.println( "PairManagerTunnelHander: incoming message - session=" + session + ", payload=" + data + " from " + tunnel_originator + " via " + originator); SRP6Server server; BigInteger B; synchronized( server_map ){ Object[] entry = server_map.get( session ); if ( entry == null ){ long diff = SystemTime.getMonotonousTime() - last_server_create_time; if ( diff < 5000 ){ try{ long sleep = 5000 - diff; System.out.println( "Sleeping for " + sleep + " before starting srp" ); Thread.sleep( sleep ); }catch( Throwable e ){ } } server = new SRP6Server(); server.init( N_3072, G_3072, SRP_VERIFIER, new SHA256Digest(), RandomUtils.SECURE_RANDOM ); B = server.generateServerCredentials(); server_map.put( session, new Object[]{ server, B }); last_server_create_time = SystemTime.getMonotonousTime(); total_servers++; }else{ server = (SRP6Server)entry[0]; B = (BigInteger)entry[1]; } } Long op = (Long)data.get( "op" ); if ( op == 1 ){ result.put( "op", 2 ); result.put( "s", SRP_SALT ); result.put( "b", B.toByteArray()); good_request = true; if ( data.containsKey( "test" )){ manager.recordRequest( "SRP Test", originator.getAddress().getHostAddress(), true ); } }else if ( op == 3 ){ boolean log_error = true; try{ long diff = SystemTime.getMonotonousTime() - last_server_agree_time; if ( diff < 5000 ){ try{ long sleep = 5000 - diff; System.out.println( "Sleeping for " + sleep + " before completing srp" ); Thread.sleep( sleep ); }catch( Throwable e ){ } } BigInteger A = new BigInteger((byte[])data.get( "a" )); BigInteger serverS = server.calculateSecret( A ); byte[] shared_secret = serverS.toByteArray(); Cipher decipher = Cipher.getInstance ("AES/CBC/PKCS5Padding"); byte[] key = new byte[16]; System.arraycopy( shared_secret, 0, key, 0, 16 ); SecretKeySpec secret = new SecretKeySpec( key, "AES"); decipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec((byte[])data.get( "enc_iv" ))); byte[] dec = decipher.doFinal( (byte[])data.get( "enc_data" )); String json_str = new String( dec, "UTF-8" ); if ( !json_str.startsWith( "{" )){ log_error = false; throw( new Exception( "decode failed" )); } JSONObject dec_json = (JSONObject)JSONUtils.decodeJSON( json_str ); String tunnel_url = (String)dec_json.get( "url" ); String service_id = new String((byte[])data.get( "service"), "UTF-8" ); String endpoint_url = (String)dec_json.get( "endpoint"); boolean ok = createTunnel( tunnel_originator, session, service_id, secret, tunnel_url, endpoint_url ); result.put( "op", 4 ); result.put( "status", ok?"ok":"failed" ); good_request = true; }catch( Throwable e ){ result.put( "op", 4 ); result.put( "status", "failed" ); // filter usual errors on bad agreement if ( e instanceof BadPaddingException || e instanceof IllegalBlockSizeException ){ log_error = false; } if ( log_error ){ e.printStackTrace(); } }finally{ last_server_agree_time = SystemTime.getMonotonousTime(); } } return( result ); }finally{ if ( !good_request ){ manager.recordRequest( "SRP", originator.getAddress().getHostAddress(), false ); } } } }); SimpleTimer.addPeriodicEvent( "pm:tunnel:stats", 30*1000, new TimerEventPerformer() { public void perform( TimerEvent event) { synchronized( tunnels ){ if ( tunnels.size() > 0 ){ System.out.println( "PairTunnels: " + tunnels.size()); for ( PairManagerTunnel t: tunnels.values()){ System.out.println( "\t" + t.getString()); } } } } }); }catch( Throwable e ){ Debug.out( e ); init_fail = Debug.getNestedExceptionMessage( e ); manager.updateSRPState(); } } protected String getStatus() { if ( init_fail != null ){ return( MessageText.getString( "MyTorrentsView.menu.setSpeed.disabled" ) + ": " + init_fail ); }else if ( !active ){ return( MessageText.getString( "pairing.status.initialising" ) + "..." ); }else if ( SRP_SALT == null ){ return( MessageText.getString( "pairing.srp.pw.req" )); }else if ( last_punchers_registered == 0 ){ return( MessageText.getString( "pairing.srp.registering" )); }else{ return( MessageText.getString( "tps.status.available" )); } } protected void setActive( boolean a ) { synchronized( this ){ if ( active == a ){ return; } active = a; } updateActive(); } private void updateActive() { manager.updateSRPState(); if ( active && SRP_VERIFIER != null ){ start(); }else{ synchronized( tunnels ){ for ( PairManagerTunnel t: new ArrayList<PairManagerTunnel>( tunnels.values())){ t.destroy(); } } synchronized( local_server_map ){ local_server_map.clear(); } } List<DHTNATPuncher> punchers = new ArrayList<DHTNATPuncher>(); punchers.addAll( nat_punchers_ipv4 ); punchers.addAll( nat_punchers_ipv6 ); for ( DHTNATPuncher p: punchers ){ p.forceActive( active ); } } protected void updateRegistrationData( Map<String,Object> payload ) { int puncher_num = 0; int num_registered = 0; for ( DHTNATPuncher nat_ipv4: nat_punchers_ipv4 ){ DHTTransportContact rend = nat_ipv4.getRendezvous(); DHTTransportContact lc = nat_ipv4.getLocalContact(); if ( rend != null && lc != null ){ puncher_num++; InetSocketAddress rend_address = rend.getTransportAddress(); num_registered++; payload.put( "rc_v4-" + puncher_num, rend_address.getAddress().getHostAddress() + ":" + rend_address.getPort()); if ( puncher_num == 1 ){ payload.put( "rl_v4", lc.getExternalAddress().getAddress().getHostAddress() + ":" + lc.getAddress().getPort()); } } } puncher_num = 0; for ( DHTNATPuncher nat_ipv6: nat_punchers_ipv6 ){ DHTTransportContact rend = nat_ipv6.getRendezvous(); DHTTransportContact lc = nat_ipv6.getLocalContact(); if ( rend != null && lc != null ){ puncher_num++; InetSocketAddress rend_address = rend.getTransportAddress(); num_registered++; payload.put( "rc_v6-" + puncher_num, rend_address.getAddress().getHostAddress() + ":" + rend_address.getPort()); if ( puncher_num == 1 ){ payload.put( "rl_v6", lc.getExternalAddress().getAddress().getHostAddress() + ":" + lc.getAddress().getPort()); } } } if ( num_registered != last_punchers_registered ){ last_punchers_registered = num_registered; manager.updateSRPState(); } } private BigInteger fromHex( String hex ) { return new BigInteger(1, ByteFormatter.decodeString( hex.replaceAll( " ", "" ))); } protected boolean handleLocalTunnel( TrackerWebPageRequest request, TrackerWebPageResponse response ) throws IOException { start(); if ( SRP_VERIFIER == null || !active ){ throw( new IOException( "Secure pairing is not enabled" )); } boolean good_request = false; try{ // remove /pairing/tunnel/ String url = request.getURL().substring( 16 ); int q_pos = url.indexOf( '?' ); Map<String,String> args = new HashMap<String,String>(); if ( q_pos != -1 ){ String args_str = url.substring( q_pos+1 ); String[] bits = args_str.split( "&" ); for ( String arg: bits ){ String[] x = arg.split( "=" ); if ( x.length == 2 ){ args.put( x[0].toLowerCase(), x[1] ); } } url = url.substring( 0, q_pos ); } if ( url.startsWith( "create" )){ String ac = args.get( "ac" ); String sid = args.get( "sid" ); if ( ac == null || sid == null ){ throw( new IOException( "Access code or service id missing" )); } if ( !ac.equals( manager.peekAccessCode())){ throw( new IOException( "Invalid access code" )); } PairedServiceImpl ps = manager.getService( sid ); if ( ps == null ){ good_request = true; throw( new IOException( "Service '" + sid + "' not registered" )); } PairedServiceRequestHandler handler = ps.getHandler(); if ( handler == null ){ good_request = true; throw( new IOException( "Service '" + sid + "' has no handler registered" )); } JSONObject json = new JSONObject(); JSONObject result = new JSONObject(); json.put( "result", result ); byte[] ss = new byte[]{ SRP_SALT[0], SRP_SALT[1], SRP_SALT[2], SRP_SALT[3] }; long tunnel_id = RandomUtils.nextSecureAbsoluteLong(); String tunnel_name = Base32.encode( ss ) + "_" + tunnel_id; synchronized( local_server_map ){ long diff = SystemTime.getMonotonousTime() - last_local_server_create_time; if ( diff < 5000 ){ try{ long sleep = 5000 - diff; System.out.println( "Sleeping for " + sleep + " before starting srp" ); Thread.sleep( sleep ); }catch( Throwable e ){ } } SRP6Server server = new SRP6Server(); server.init( N_3072, G_3072, SRP_VERIFIER, new SHA256Digest(), RandomUtils.SECURE_RANDOM ); BigInteger B = server.generateServerCredentials(); local_server_map.put( tunnel_name, new Object[]{ server, handler, null, null }); last_local_server_create_time = SystemTime.getMonotonousTime(); total_local_servers++; result.put( "srp_salt", Base32.encode( SRP_SALT )); result.put( "srp_b", Base32.encode( B.toByteArray())); Map<String,String> headers = request.getHeaders(); String host = headers.get( "host" ); // remove port number int pos = host.lastIndexOf( "]" ); if ( pos != -1 ){ // ipv6 literal host = host.substring( 0, pos+1 ); }else{ pos = host.indexOf( ':' ); if ( pos != -1 ){ host = host.substring( 0, pos ); } } String abs_url = request.getAbsoluteURL().toString(); // unfortunately there is some nasty code that uses a configured tracker // address as the default host abs_url = UrlUtils.setHost( new URL( abs_url ), host).toExternalForm(); pos = abs_url.indexOf( "/create" ); String tunnel_url = abs_url.substring(0,pos) + "/id/" + tunnel_name; result.put( "url", tunnel_url ); } response.getOutputStream().write( JSONUtils.encodeToJSON( json ).getBytes( "UTF-8" )); response.setContentType( "application/json; charset=UTF-8" ); response.setGZIP( true ); good_request = true; return( true ); }else if ( url.startsWith( "id/" )){ String tunnel_name = url.substring( 3 ); Object[] entry; synchronized( local_server_map ){ entry = local_server_map.get( tunnel_name ); if ( entry == null ){ good_request = true; throw( new IOException( "Unknown tunnel id" )); } } String srp_a = args.get( "srp_a" ); String enc_data = args.get( "enc_data" ); String enc_iv = args.get( "enc_iv" ); if ( srp_a != null && enc_data != null && enc_iv != null ){ try{ synchronized( local_server_map ){ long diff = SystemTime.getMonotonousTime() - last_local_server_agree_time; if ( diff < 5000 ){ try{ long sleep = 5000 - diff; System.out.println( "Sleeping for " + sleep + " before completing srp" ); Thread.sleep( sleep ); }catch( Throwable e ){ } } } JSONObject json = new JSONObject(); JSONObject result = new JSONObject(); json.put( "result", result ); SRP6Server server = (SRP6Server)entry[0]; BigInteger A = new BigInteger( Base32.decode( srp_a )); BigInteger serverS = server.calculateSecret( A ); byte[] shared_secret = serverS.toByteArray(); Cipher decipher = Cipher.getInstance ("AES/CBC/PKCS5Padding"); byte[] key = new byte[16]; System.arraycopy( shared_secret, 0, key, 0, 16 ); SecretKeySpec secret = new SecretKeySpec( key, "AES"); decipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec( Base32.decode( enc_iv ))); byte[] dec = decipher.doFinal( Base32.decode( enc_data )); JSONObject dec_json = (JSONObject)JSONUtils.decodeJSON( new String( dec, "UTF-8" )); String tunnel_url = (String)dec_json.get( "url" ); if ( !tunnel_url.contains( tunnel_name )){ throw( new IOException( "Invalid tunnel url" )); } String endpoint_url = (String)dec_json.get( "endpoint"); entry[2] = secret; entry[3] = endpoint_url; result.put( "state", "activated" ); response.getOutputStream().write( JSONUtils.encodeToJSON( json ).getBytes( "UTF-8" )); response.setContentType( "application/json; charset=UTF-8" ); response.setGZIP( true ); good_request = true; return( true ); }catch( Throwable e ){ throw( new IOException( Debug.getNestedExceptionMessage( e ))); }finally{ last_local_server_agree_time = SystemTime.getMonotonousTime(); } }else if ( args.containsKey( "close" )){ synchronized( local_server_map ){ local_server_map.remove( tunnel_name ); } good_request = true; return( true ); }else{ PairedServiceRequestHandler request_handler = (PairedServiceRequestHandler)entry[1]; SecretKeySpec secret = (SecretKeySpec)entry[2]; String endpoint_url = (String)entry[3]; if ( secret == null ){ throw( new IOException( "auth not completed" )); } byte[] request_data = FileUtil.readInputStreamAsByteArray( request.getInputStream()); try{ byte[] decrypted; { byte[] IV = new byte[16]; System.arraycopy( request_data, 0, IV, 0, IV.length ); Cipher decipher = Cipher.getInstance ("AES/CBC/PKCS5Padding"); decipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec( IV )); decrypted = decipher.doFinal( request_data, 16, request_data.length-16 ); } byte[] reply_bytes = request_handler.handleRequest( request.getClientAddress2().getAddress(), endpoint_url, decrypted ); { Cipher encipher = Cipher.getInstance ("AES/CBC/PKCS5Padding"); encipher.init( Cipher.ENCRYPT_MODE, secret ); AlgorithmParameters params = encipher.getParameters (); byte[] IV = params.getParameterSpec(IvParameterSpec.class).getIV(); byte[] enc = encipher.doFinal( reply_bytes ); byte[] rep_bytes = new byte[ IV.length + enc.length ]; System.arraycopy( IV, 0, rep_bytes, 0, IV.length ); System.arraycopy( enc, 0, rep_bytes, IV.length, enc.length ); response.getOutputStream().write( rep_bytes ); response.setContentType( "application/octet-stream" ); good_request = true; return( true ); } }catch( Throwable e ){ throw( new IOException( Debug.getNestedExceptionMessage( e ))); } } } throw( new IOException( "Unknown tunnel operation" )); }finally{ if ( !good_request ){ manager.recordRequest( "SRP", request.getClientAddress2().getAddress().getHostAddress(), false ); } } } private boolean createTunnel( InetAddress originator, long session, String sid, SecretKeySpec secret, String tunnel_url, String endpoint_url ) { PairedServiceImpl ps = manager.getService( sid ); if ( ps == null ){ Debug.out( "Service '" + sid + "' not registered" ); return( false ); } PairedServiceRequestHandler handler = ps.getHandler(); if ( handler == null ){ Debug.out( "Service '" + sid + "' has no handler registered" ); return( false ); } String key = originator.getHostAddress() + ":" + session + ":" + sid; synchronized( tunnels ){ PairManagerTunnel existing = tunnels.get( key ); if ( existing != null ){ return( true ); } if ( tunnels.size() > MAX_TUNNELS ){ long oldest_active = Long.MAX_VALUE; PairManagerTunnel oldest_tunnel = null; for ( PairManagerTunnel t: tunnels.values()){ long at = t.getLastActive(); if ( at < oldest_active ){ oldest_active = at; oldest_tunnel = t; } } oldest_tunnel.destroy(); tunnels.remove( oldest_tunnel.getKey()); } PairManagerTunnel tunnel = new PairManagerTunnel( this, key, originator, sid, handler, secret, tunnel_url, endpoint_url ); tunnels.put( key, tunnel ); System.out.println( "Created pair manager tunnel: " + tunnel.getString()); } return( true ); } protected void closeTunnel( PairManagerTunnel tunnel ) { System.out.println( "Destroyed pair manager tunnel: " + tunnel.getString()); synchronized( tunnels ){ tunnels.remove( tunnel.getKey()); } } protected void generateEvidence( IndentWriter writer ) { writer.println( "Tunnel Handler" ); writer.indent(); writer.println( "started=" + started + ", active=" + active ); if ( init_fail != null ){ writer.println( "Init fail: " + init_fail ); } long now = SystemTime.getMonotonousTime(); writer.println( "total local=" + total_local_servers ); writer.println( "last local create=" + (last_local_server_create_time==0?"<never>":String.valueOf(now-last_local_server_create_time))); writer.println( "last local agree=" + (last_local_server_agree_time==0?"<never>":String.valueOf(now-last_local_server_agree_time))); writer.println( "total remote=" + total_servers ); writer.println( "last remote create=" + (last_server_create_time==0?"<never>":String.valueOf(now-last_server_create_time))); writer.println( "last remote agree=" + (last_server_agree_time==0?"<never>":String.valueOf(now-last_server_agree_time))); synchronized( tunnels ){ writer.println( "tunnels=" + tunnels.size()); for ( PairManagerTunnel tunnel: tunnels.values()){ writer.println( " " + tunnel.getString()); } } try{ writer.println( "IPv4 punchers: " + nat_punchers_ipv4.size()); for ( DHTNATPuncher p: nat_punchers_ipv4 ){ writer.println( " " + p.getStats()); } writer.println( "IPv6 punchers: " + nat_punchers_ipv6.size()); for ( DHTNATPuncher p: nat_punchers_ipv6 ){ writer.println( " " + p.getStats()); } }finally{ writer.exdent(); } } }